home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
pcr
/
pcr4_4.lha
/
DIST
/
gc
/
GCcollector.c
< prev
next >
Wrap
C/C++ Source or Header
|
1991-10-21
|
17KB
|
563 lines
/* begincopyright
Copyright (c) 1988,1990 Xerox Corporation. All rights reserved.
Use and copying of this software and preparation of derivative works based
upon this software are permitted. Any distribution of this software or
derivative works must comply with all applicable United States export
control laws. This software is made available AS IS, and Xerox Corporation
makes no warranty about the software, its performance or its conformity to
any specification. Any person obtaining a copy of this software is requested
to send their name and post office or electronic mail address to:
PCR Coordinator
Xerox PARC
3333 Coyote Hill Rd.
Palo Alto, CA 94304
Parts of this software were derived from code bearing the copyright notice:
Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers
This material may be freely distributed, provided this notice is retained.
This material is provided as is, with no warranty expressed or implied.
Use at your own risk.
endcopyright */
/*
* This file contains the functions:
* void GC_exclusive_mark_inner()
* void GC_exclusive_mark()
* XR_Pointer GC_Full_Mark_Inner(self)
* void GC_full_parallel_mark()
* XR_Pointer GC_Partial_Mark_Inner(self)
* void GC_parallel_mark()
* void GC_asynchronous_mark(which_roots)
* void GC_freeze_and_start_reclaim()
* void GC_partial_collection()
* void GC_full_collection()
*/
/*
* Barry, April 16, 1991 10:26:47 am PST
* Boehm, May 31, 1991 11:49:24 am PDT
*/
# include <signal.h>
# include <sys/types.h>
# include <sys/times.h>
# include <sys/timeb.h>
# include "xr/ThreadsMsg.h"
# include "xr/Threads.h"
# include "xr/GCPrivate.h"
# define CHECK_INVARIANTS
# undef CHECK_INVARIANTS
# define DEBUG_TIMING
# undef DEBUG_TIMING
# ifdef CHECK_INVARIANTS
# include "xr/ThreadsBackdoor.h"
# endif
#define I_HOLD_ML(ml) (((XR_ML)(ml))->ml_holder == XR_currThread)
static XR_MesaProc ParPartialMarkProc;
static XR_MesaProc ParFullMarkProc;
static bool GC_full_gc = FALSE; /* This is a full collection */
/* protected by allocate_ml. */
/* Used only for callbacks. */
static bool GC_clean_roots_marked = FALSE;
/* Objects directly reachable from */
/* clean roots have been marked. */
/* Reset to False after mark bits are */
/* cleared. Not valid while marker is */
/* running. */
/* Condition variable used to signal end of collection. Protected by */
/* GC_allocate_ml. Has short timeout. */
static struct XR_CVRep gc_done_cv;
/* Priority of parallel marker. Note that it gets woken up periodically */
/* by a much higher priority process. */
static XR_Pri gcParallelPriority = XR_PRI_USER_BACKGROUND; /* slightly > IDLE */
void GC_full_collection(stop_the_world)
bool stop_the_world; /* Stop the world during collection, even */
/* when not otherwise indicated. */
{
# ifdef VISUALIZE
update_message("Full gc");
# endif
# ifdef BLACK_LIST
GC_prepare_to_clear_black_list();
# endif
if (!stop_the_world && GC_should_pre_collect()) {
GC_partial_collection();
}
XR_LogVMsg "Starting full marking\n");
GC_reclaim_useful_pages(); /* Must not hold GC_allocate_ml */
GC_clean_roots_marked = FALSE;
GC_clear_marks(); /* Safe because of immediately preceding */
/* GC_reclaim_useful_pages. */
GC_revoke_all_tenure();
GC_tenure_count = 0;
GC_markCarefully = GC_should_mark_carefully();
/* In parallel mode we just mark all the reachable stuff in parallel,
and then do a partial collection when it's needed. */
if (! stop_the_world && (GC_mode & GC_PARALLEL)) {
GC_full_parallel_mark();
GC_partial_collection();
/* Make entirely empty pages available immediately. Heap growth */
/* is otherwise likely. */
XR_MonitorEntry(&GC_allocate_ml);
GC_reclaim_empty_pages();
XR_MonitorExit(&GC_allocate_ml);
} else {
/* clearing the dirty bits happens in parallel in
GC_parallel_mark if we're doing it in parallel, as above. */
XR_MonitorEntry(&GC_allocate_ml);
GC_freeze_and_start_reclaim();
XR_Broadcast(&gc_done_cv);
GC_reclaim_empty_pages();
XR_MonitorExit(&GC_allocate_ml);
}
GC_markCarefully = FALSE;
GC_max_markfaults = 0;
# ifdef BLACK_LIST
GC_clear_black_list();
# endif
}
void GC_partial_collection()
{
XR_MonitorEntry(&GC_allocate_ml);
# ifdef VISUALIZE
update_message("Partial gc");
# endif
if (GC_mode & GC_PARALLEL) {
GC_reclaim_empty_pages();
GC_parallel_mark();
}
GC_freeze_and_start_reclaim();
XR_Broadcast(&gc_done_cv);;
XR_MonitorExit(&GC_allocate_ml);
}
/*
* Restore inaccessible objects to the free list or queue them for later
* reclamation.
* Update GC_mem_found (number of reclaimed longwords after garbage collection)
* A call to GC_gcollect actually finishes the reclaim phase of the last
* collection, and then starts the new collection.
*
*/
void GC_freeze_and_start_reclaim()
{
extern void GC_mark_regs();
# ifdef PRINTTIMES
unsigned long start_faults = GC_check_faults();
unsigned long start_wall_clock_time = time((time_t *)0);
/* Times in milliseconds */
long start_time = 0;
long flush_time = 0;
long mark_time = 0;
long reclaim_time = 0;
static struct tms time_buf;
# define FTIME \
IDIV((100*(time_buf.tms_utime + time_buf.tms_stime)), \
(LOCAL_HZ/10))
/* Get starting time */
times(&time_buf);
start_time = FTIME;
# endif
# ifdef DIRTY_BITS
/* Minimize protection violations while the world is stopped. */
GC_unprotect_headers();
# endif
/* Finish last collection and update gc counter */
# ifdef PRINTBLOCKS
GC_vprintf("\n");
# endif
# ifdef PRINTSTATS
GC_printf("--> Completing collection %d:\n", GC_gc_no);
GC_vprintf("Reclaimed %d bytes (%d sm. obj. blocks) before flush\n",
WORDS_TO_BYTES(GC_mem_found), GC_reclaim_count);
# endif
GC_reclaim_empty_pages();
# ifdef PRINTTIMES
/* Get intermediate time */
times(&time_buf);
flush_time = FTIME;
# endif
# ifdef PRINTBLOCKS
GC_vprintf("\n");
# endif
# ifdef PRINTSTATS
GC_printf("Reclaimed total of %d bytes in heap of size %d bytes\n",
WORDS_TO_BYTES(GC_mem_found), GC_heapsize);
GC_vprintf("Found %d tenurable blocks\n",
GC_tenure_count);
# endif
GC_zero_reclaim_lists();
/* Advance counters for next collection */
GC_gc_no++;
GC_full_gc = (GC_my_objects_in_use == 0); /* No previous marking */
# ifdef PRINTSTATS
GC_printf("--> Starting %sGC %d after %d allocated bytes\n",
(GC_full_gc? "full " : ""), GC_gc_no, 4 * GC_words_allocd);
# endif
GC_mem_found = GC_mem_freed;
GC_mem_freed = 0;
GC_words_allocd_before_gc += GC_words_allocd;
GC_words_allocd = 0;
GC_objects_allocd_before_gc += GC_objects_allocd;
GC_objects_allocd = 0;
# ifdef PRINTSTATS
GC_reclaim_count = 0;
# endif
/* The only part of the entire garbage collector that needs to be
run with the world stopped. But notice that when we're done here
we have a completely empty heap, and any process that starts up to
allocate will instantly wedge anyway, since we have the allocation lock.
Likewise, all of the finalization queues are protected by the allocate
lock, and we can muck about with the queues in the non-exclusive part
of the code. */
GC_exclusive_mark();
# ifdef PRINTTIMES
/* Get intermediate time */
times(&time_buf);
mark_time = FTIME;
# endif
# ifdef PRINTSTATS
GC_printf("Found %d (atomic) + %d (composite) active bytes\n",
WORDS_TO_BYTES(GC_my_atomic_in_use),
WORDS_TO_BYTES(GC_my_composite_in_use));
GC_vprintf("Bytes recovered before reclaim = %d\n",
WORDS_TO_BYTES(GC_mem_found));
# endif
/* Update public statistics to refelect private ones */
GC_composite_in_use = GC_my_composite_in_use;
GC_atomic_in_use = GC_my_atomic_in_use;
GC_objects_in_use = GC_my_objects_in_use;
/* Reclaim large objects and enqueue other blocks for sweeping */
GC_reclaim();
# ifdef PRINTSTATS
GC_vprintf("Found total of %d bytes by end of initial reclamation\n",
WORDS_TO_BYTES(GC_mem_found));
# endif
# ifdef PRINTTIMES
times(&time_buf);
reclaim_time = FTIME;
# endif
if (GC_markfaults > GC_max_markfaults) {
GC_max_markfaults = GC_markfaults;
}
/* Print GC times */
# ifdef PRINTTIMES
if (XR_sysArea->sa_numVP == 1) {
GC_printf(
"GC took %d(flush) + %d(mark) + %d(ss) msecs\n",
flush_time - start_time, mark_time - flush_time,
reclaim_time - mark_time);
} /* O.w. numbers probably came from different vps and are bogus */
GC_vprintf(
"Elapsed time: %d secs, paused GC faults: %d (%d all marking)\n",
time((time_t *)0) - start_wall_clock_time,
GC_check_faults() - start_faults, GC_markfaults);
# endif
GC_markfaults = 0;
}
/*
* Mark from dirty marked objects, and from roots as
* specified by which_roots (one of ALL_POINTERS, DEFINITELY_DIRTY_POINTERS,
* and POSSIBLY_DIRTY_POINTERS).
* This always preserves the invariant that marked objects on clean
* pages point to marked objects.
*/
void GC_asynchronous_mark(which_roots)
int which_roots;
{
int ans;
# ifdef PRINTTIMES
unsigned long start_wall_clock_time = time((time_t *)0);
# endif
# ifdef PRINTSTATS
GC_printf("--> Starting asynchronous marking for collection %d:\n",
GC_gc_no + 1);
# endif
/* Mark from dirty pages */
/* If this is a full collection there are no marked objects */
/* GC_mark_rescuers should just call/return */
GC_mark_rescuers(ALIGNMENT);
/* Mark objects reachable from roots */
/* Get a rough snapshot of the roots. This is inaccurate, since */
/* threads are still going. We assume only that roots we miss */
/* that are in the heap will be dirtied when they become */
/* interesting, and that anything we get is addressable. */
GC_mark_roots(which_roots);
GC_mark(ALIGNMENT);
# ifdef PRINTTIMES
GC_printf("Asynchronous marking took %d seconds\n",
time((time_t *)0) - start_wall_clock_time);
# endif
}
/* Run the parallel marker */
/* Wait for this process to complete before returning. */
void GC_parallel_mark()
{
if (!I_HOLD_ML(&GC_allocate_ml)) {
GC_abort("GC_parallel_mark 0");
}
XR_MonitorExit(&GC_allocate_ml);
GC_RunParallel(ParPartialMarkProc, TRUE);
XR_MonitorEntry(&GC_allocate_ml);
}
/* Run the marking algorithm to clean pages.
Should only be called via above, I think. */
XR_Pointer GC_Partial_Mark_Inner(self)
XR_MesaProc self;
{
unsigned long initial_words_allocd;
XR_Pri initial_pri = XR_GetPriority();
# ifdef DEBUG_TIMING
struct timeval old_t;
struct timeval mid_t;
struct timeval new_t;
# endif
if (I_HOLD_ML(&GC_allocate_ml)) {
GC_abort("GC_Partial_Mark_Inner 0");
}
# ifdef DEBUG_TIMING
XR_GetTimeOfDay(&old_t,0);
# endif
GC_lock_to_fixed_vp();
# ifdef DEBUG_TIMING
XR_GetTimeOfDay(&mid_t,0);
# endif
initial_words_allocd = GC_words_allocd;
GC_get_and_clear_dirty_bits();
# ifdef DEBUG_TIMING
XR_GetTimeOfDay(&new_t,0);
XR_ConsoleMsg("Acquired dirty bits in %d + %d usecs\n",
1000000 * (mid_t.tv_sec - old_t.tv_sec)
+ mid_t.tv_usec - old_t.tv_usec,
1000000 * (new_t.tv_sec - mid_t.tv_sec)
+ new_t.tv_usec - mid_t.tv_usec);
# endif
XR_SetPriority(gcParallelPriority);
/* After GC_get_and_clear_dirty_bits, to make it easier to get lock. */
GC_propagate_dirty_bits();
/* This has to change to get multi-collection tenuring in.
There are two divisions, tho. What pages should be searched,
??? */
GC_asynchronous_mark(POSSIBLY_DIRTY_POINTERS);
if (GC_should_do_second_mark(initial_words_allocd)) {
XR_SetPriority(initial_pri);
GC_get_and_clear_dirty_bits();
XR_SetPriority(gcParallelPriority);
GC_propagate_dirty_bits();
GC_asynchronous_mark(DEFINITELY_DIRTY_POINTERS);
}
GC_parallel_done();
return(0);
}
/* Run a full mark process. Do not return until it's done. */
void GC_full_parallel_mark()
{
if (I_HOLD_ML(&GC_allocate_ml)) {
GC_abort("GC_full_parallel_mark 0");
}
GC_RunParallel(ParFullMarkProc, TRUE);
}
/* Run just the marking algorithm, but mark from roots as well. */
/* Broadcast gc_done_cv when done. */
/* Should only be called from the routine above. */
XR_Pointer GC_Full_Mark_Inner(self)
XR_MesaProc self;
{
int ans;
if (I_HOLD_ML(&GC_allocate_ml)) {
GC_abort("GC_Full_Mark_Inner 0");
}
# ifdef PRINTSTATS
GC_printf("--> Starting parallel marking for full collection\n");
# endif
GC_lock_to_fixed_vp();
GC_clear_dirty_bits();
XR_SetPriority(gcParallelPriority);
/* After GC_clear_dirty_bits, to make it easier to get lock. */
/* I: "clean marked objects point to marked objects" holds vacuously */
GC_asynchronous_mark(ALL_POINTERS);
/* I again holds here. */
GC_clean_roots_marked = TRUE;
GC_parallel_done();
return(0);
}
void GC_exclusive_mark()
{
struct GC_InfoRep ir;
/* Run GC_exclusive_mark_inner with all other processes
stopped. This is, in a way, the way to aquire the lock on the
real vm dirty bits. Nobody else will dirty any pages while we're
running exclusive. */
if( !I_HOLD_ML(&GC_allocate_ml) )
XR_Panic("GC_exclusive_mark 0");
ir.gci_full_collection = GC_full_gc;
if (GC_callBackBefore != NIL ) {
(*GC_callBackBefore)(GC_callBackBeforeClientData, &ir);
}
GC_RunExclusive( GC_exclusive_mark_inner, 0 );
if (GC_callBackAfter != NIL ) {
(*GC_callBackAfter)(GC_callBackAfterClientData, &ir);
}
}
void GC_exclusive_mark_inner()
{
if( !GC_running_exclusive )
XR_Panic("GC_exclusive_mark_inner 0");
# ifdef CHECK_INVARIANTS
GC_print_thread_stack(1);
# endif
/* Correctly set dirty bits. We clear them way down
at the bottom, and since we're always running
alone, and don't dirty any pages of consequence,
this is as if we had done an atomic GC_get_and_clear_dirty_bits */
GC_get_dirty_bits();
GC_propagate_dirty_bits();
/* Mark from marked objects on dirty pages */
GC_mark_rescuers(ALIGNMENT);
GC_mark_roots(GC_clean_roots_marked?
POSSIBLY_DIRTY_POINTERS : ALL_POINTERS);
GC_clean_roots_marked = TRUE;
/* Roots are now on mark stack. Mark through them. */
GC_mark(ALIGNMENT);
/* Finalize */
/* Doesn't really need to run exclusively. But the callback does, */
/* and this must precede GC_callBackDuring and GC_clear_fl_marks. */
/* Not much gets done while we're holding the allocate lock */
/* in any case. */
# ifdef FINALIZE
{
# ifdef PRINTSTATS
GC_vprintf("Starting marking for finalization.\n");
# endif
GC_TraceFinalizableObjects();
}
# endif /* FINALIZE */
#ifdef CHECK_INVARIANTS
GC_printf("Checking invariants...");
GC_check_invariants_before_reclaim();
GC_print_thread_stack(1);
GC_printf("Done checking invariants\n");
#endif
/* Clear mark bits for objects on free list, in case they accidentally */
/* got marked. */
GC_clear_free_lists();
if (GC_callBackDuring != NIL ) {
struct GC_InfoRep ir;
ir.gci_full_collection = GC_full_gc;
(*GC_callBackDuring)(GC_callBackDuringClientData, &ir);
}
/* poor name? Do this if the GC owns the dirty bits? */
/* Also, since the only things going on between the get and the clear
are in the marks, [well, alomst the only things] can we just
make this a GC_get_and_clear_dirty_bits? */
if (GC_mode & DIRTY_BITS_REQUIRED) {
GC_clear_dirty_bits();
}
}
/* Wait for collection to complete, hurrying it along a bit */
/* Dont wait more than timeout ticks. */
void GC_wait_for_gc(timeout)
XR_Ticks timeout;
{
int current_collection = GC_gc_no;
register XR_Ticks i;
if (timeout == XR_WAIT_FOREVER) timeout = XR_END_OF_TIME;
GC_start_daemon();
if( ! I_HOLD_ML(&(GC_allocate_ml)) )
XR_Panic("GC_wait_for_gc 0");
for (i = 0; i < timeout; i++) {
if (GC_gc_no > current_collection) break;
XR_WaitCV(&gc_done_cv, &GC_allocate_ml);
if (GC_gc_no == current_collection && (i & 3) == 3) {
GC_demand_and_dont_wait();
}
}
}
void GC_collector_setup()
{
ParPartialMarkProc = XR_MakeMesaProc(GC_Partial_Mark_Inner, 0);
ParFullMarkProc = XR_MakeMesaProc(GC_Full_Mark_Inner, 0);
XR_InitializeCondition(&gc_done_cv, ((XR_Ticks)1));
}